/*
 * 版权所有 (C) 2015 知启蒙(ZHIQIM) 保留所有权利。[遇见知启蒙，邂逅框架梦]
 * 
 * https://zhiqim.org/project/zhiqim_framework/zhiqim_kernel.htm
 *
 * Zhiqim Kernel is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *          http://license.coscl.org.cn/MulanPSL2
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
 */
package org.zhiqim.kernel.util;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.Locale;
import java.util.ResourceBundle;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

import org.zhiqim.kernel.annotation.AnAlias;
import org.zhiqim.kernel.constants.CodeConstants;
import org.zhiqim.kernel.model.Filter;
import org.zhiqim.kernel.model.FilterHandler;

/**
 * 资源相关工具类
 *
 * @version v1.0.0 @author zouzhigang 2014-2-27 新建与整理
 */
@AnAlias("Resources")
public class Resources implements CodeConstants
{
    /**
     * 扫描所有类路径，根据提供的过滤器和处理器处理
     * 
     * @param filter    过滤器
     * @param handler   处理器
     * @return          是否扫描成功
     */
    public static boolean scanClassPath(Filter filter, FilterHandler handler)
    {
        return scanClassPath(filter, handler, Lists.toList(Systems.getClassPaths()));
    }
    
    /**
     * 扫描指定类路径，根据提供的过滤器和处理器处理
     * 
     * @param filter    过滤器
     * @param handler   处理器
     * @param pathList  指定类路径
     * @return          是否扫描成功
     */
    public static boolean scanClassPath(Filter filter, FilterHandler handler, List<String> pathList)
    {
        for (String path : pathList)
        {
            File file = new File(path);
            if (!Files.exists(file) || !file.canRead() || (!file.isFile() && !file.isDirectory()))
                continue;
            
            if (file.isDirectory())
            {//.class目录
                try
                {
                    List<File> fileList = new ArrayList<File>();
                    Files.queryFilterList(fileList, file, filter);

                    for (File classFile : fileList)
                    {
                        if (!handler.handle(new Object[]{file, classFile}))
                            return false;
                    }
                }
                catch (Throwable e){continue;}
            }
            else if (file.isFile() && (Strings.endsWithIgnoreCase(path, ".jar") || Strings.endsWithIgnoreCase(path, ".zip")))
            {//jar/zip文件
                try
                {
                    JarFile jarFile = new JarFile(path);
                    List<JarEntry> entryList = Jars.queryFilterList(jarFile, filter);
                    
                    for (JarEntry jarEntry : entryList)
                    {
                        if (!handler.handle(new Object[]{jarFile, jarEntry}))
                            return false;
                    }
                }
                catch (Throwable e){continue;}
            }
        }
        
        return true;
    }
    
    /**
     * 判断资源是否存在，资源路径，如/org/zhiqim/manager/resource/conf/config.xml
     * 
     * @param path  资源路径
     * @return      true/false 存在则返回true
     */
    public static boolean exists(String path)
    {
        path = Strings.removeStartsWith(path, "/");
        return Thread.currentThread().getContextClassLoader().getResource(path) != null;
    }
    
    /**
     * 判断资源是否存在，资源路径，如/org/zhiqim/manager/resource/conf/config.xml
     * 
     * @param cls   资源能加载到的类
     * @param path  资源路径
     * @return      true/false 存在则返回true
     */
    public static boolean exists(Class<?> cls, String path)
    {
        return exists(path)?true:cls.getResource(path) != null;
    }
    
    /**
     * 获取资源URL，资源路径，如/org/zhiqim/manager/resource/conf/config.xml
     * 
     * @param path  资源路径
     * @return      得到资源URL
     */
    public static URL getResource(String path)
    {
        path = Strings.removeStartsWith(path, "/");
        return Thread.currentThread().getContextClassLoader().getResource(path);
    }
    
    /**
     * 获取资源URL，资源路径，如/org/zhiqim/manager/resource/conf/config.xml
     * 
     * @param cls   资源能加载到的类
     * @param path  资源路径
     * @return      得到资源URL
     */
    public static URL getResource(Class<?> cls, String path)
    {
        URL url = cls.getResource(path);
        return url != null?url:getResource(path);
    }
    
    /**
     * 读取资源文件流 path格式为/org/zhiqim/example/res/abc.js
     * 
     * @param path      路径
     * @return          InputStream
     */
    public static InputStream getResourceStream(String path)
    {
        path = Strings.removeStartsWith(path, "/");
        return Thread.currentThread().getContextClassLoader().getResourceAsStream(path);
    }
    
    /**
     * 读取资源文件流 path格式为/org/zhiqim/example/res/abc.js
     * 
     * @param cls       类，类和资源路径在同一JAR中，保证ClassLoader必能加载到
     * @param path      路径
     * @return          InputStream
     */
    public static InputStream getResourceStream(Class<?> cls, String path)
    {
        InputStream in = cls.getResourceAsStream(path);
        return in != null?in:getResourceStream(path);
    }
    
    /**
     * 读取资源文件 path格式为/org/zhiqim/httpd/context/service/resource/abc.js
     * 
     * @param clazz         类名，类和资源路径在同一JAR中，保证ClassLoader必能加载到
     * @param path          路径
     * @return              文件内容
     * @throws IOException  可能的异常
     */
    public static String getResourceStringUTF8(String path) throws IOException
    {
        return getResourceString(path, _UTF_8_);
    }
    
    /**
     * 读取资源文件 path格式为/org/zhiqim/httpd/context/service/resource/abc.js
     * 
     * @param cls         类名，类和资源路径在同一JAR中，保证ClassLoader必能加载到
     * @param path          路径
     * @return              文件内容
     * @throws IOException  可能的异常
     */
    public static String getResourceStringUTF8(Class<?> cls, String path) throws IOException
    {
        return getResourceString(cls, path, _UTF_8_);
    }
    
    /**
     * 读取资源文件，并将内容以字符串形式输出。 如果文件不存在，或路径错误，则返回空字符对象
     * 
     * @param path          资源类对应的资源路径
     * @param encoding      编码
     * @return              文件内容
     */
    public static String getResourceString(String path, String encoding) throws IOException
    {
        if (Validates.isEmpty(path) || Validates.isEmpty(encoding))
            return null;
        
        try
        {
            return new String(Streams.getBytesClassPath(path), encoding);
        }
        catch (IOException ex)
        {
            return null;
        }
    }
    
    /**
     * 读取资源文件，并将内容以字符串形式输出。 如果文件不存在，或路径错误，则返回空字符对象
     * 
     * @param cls           类，类和资源路径在同一JAR中，保证ClassLoader必能加载到
     * @param path          资源类对应的资源路径
     * @param encoding      编码
     * @return              文件内容
     */
    public static String getResourceString(Class<?> cls, String path, String encoding)
    {
        if (Validates.isEmpty(path) || Validates.isEmpty(encoding))
            return null;

        try
        {
            return new String(Streams.getBytesClassPath(cls, path), encoding);
        }
        catch (IOException ex)
        {
            return null;
        }
    }
    
    /**
     * 读取指资源目录下的文件名列表，不作下级目录检查，非文件或不可读的不处理
     * 
     * @param cls           搜索对应的资源类，类和资源路径在同一JAR中，保证ClassLoader必能加载到
     * @param path          搜索对应的路径
     * @return              读取指资源目录下的文件名列表
     * @throws IOException  可能的异常
     */
    public static List<String> getResourceFileNameList(Class<?> cls, String path) throws IOException
    {
        return getResourceNameList(cls, path, 1);
    }
    
    /**
     * 读取指资源目录下的目录名列表，不作下级目录检查，非目录或不可读的不处理，并过滤掉最后的/结尾
     * 
     * @param cls           搜索对应的资源类，类和资源路径在同一JAR中，保证ClassLoader必能加载到
     * @param path          搜索对应的路径
     * @return              读取指资源目录下的文件名列表
     * @throws IOException  可能的异常
     */
    public static List<String> getResourceFolderNameList(Class<?> cls, String path) throws IOException
    {
        List<String> folderList = getResourceNameList(cls, path, 2);
        for (int i=0;i<folderList.size();i++)
        {
            folderList.set(i, Strings.trimRight(folderList.get(i), "/"));
        }
        return folderList;
    }
    
    /**
     * 读取指资源目录下的文件和目录名列表，不作下级目录检查，不可读的不处理（注意目录以/结尾）
     * 
     * @param cls           搜索对应的资源类，类和资源路径在同一JAR中，保证ClassLoader必能加载到
     * @param path          搜索对应的路径
     * @return              读取指资源目录下的文件名列表
     * @throws IOException  可能的异常
     */
    public static List<String> getResourceNameList(Class<?> cls, String path) throws IOException
    {
        return getResourceNameList(cls, path, 0);
    }
    
    /**
     * 读取指资源目录下的文件名或目录名列表，不作下级目录检查，指定类型0,1,2
     * 
     * @param cls           搜索对应的资源类，类和资源路径在同一JAR中，保证ClassLoader必能加载到
     * @param path          搜索对应的路径
     * @param type          类型，0表示目录和文件，1表示文件，2表示目录
     * @return              读取指资源目录下的文件名列表
     * @throws IOException  可能的异常
     */
    public static List<String> getResourceNameList(Class<?> cls, String path, int type) throws IOException
    {
        path = Strings.removeStartsWith(path, "/");
        path = Strings.addEndsWith(path, "/");
        
        //第一步，读取资源目录列表
        List<File> fileDirList = new ArrayList<File>();
        List<JarFile> jarDirList = new ArrayList<JarFile>();
        Enumeration<URL> resources = cls.getClassLoader().getResources(path);
        while (resources.hasMoreElements())
        {
            URL resource = resources.nextElement();
            if ("file".equals(resource.getProtocol()))
            {//目录
                fileDirList.add(new File(Urls.decodeUTF8(resource.getFile())));
            }
            else if ("jar".equals(resource.getProtocol()))
            {//JAR包
                String resPath = resource.getPath();
                int ind = resPath.indexOf("!");
                if (ind == -1)
                    continue;
                
                resPath = resPath.substring(0, ind);
                resPath = Strings.removeStartsWith(resPath, "file:");
                resPath = Urls.decodeUTF8(resPath);
                jarDirList.add(new JarFile(resPath));
            }
            else if ("bundleresource".equals(resource.getProtocol()))
            {//eclipse的bundle中的资源
                try
                {
                    Class<?> clazz = Classes.forName("org.eclipse.core.runtime.FileLocator");
                    Method m = clazz.getDeclaredMethod("toFileURL", URL.class);
                    resource = (URL)m.invoke(null, resource);
                    fileDirList.add(new File(Urls.decodeUTF8(resource.getPath())));
                }
                catch (Exception e) 
                {//异常表示不支持
                }
            }
            //其他的暂不支持
        }
        
        //第二步，从目录文件和JAR中搜索
        List<String> fileNameList = new ArrayList<String>();
        
        //2.1先搜目录
        for (File fileDir : fileDirList)
        {
            File[] files = fileDir.listFiles();
            for (File file : files)
            {
                if (!file.isFile() && !file.canRead())
                    continue;
                
                fileNameList.add(file.getName());
            }
        }
        
        //2.2再搜JAR
        for (JarFile fileDir : jarDirList)
        {
            Enumeration<JarEntry> enumeration = fileDir.entries();
            while (enumeration.hasMoreElements()) 
            { 
                JarEntry entry = enumeration.nextElement();
                String name = entry.getName();
                if (!name.startsWith(path))
                    continue;//不是该目录的不要
                
                name = Strings.removeStartsWith(name, path);
                if (_EMPTY_.equals(name))
                    continue;//当前目录不要
                
                switch (type)
                {
                case 0:
                {//文件+目录
                    if (name.indexOf("/") == -1)
                        fileNameList.add(name);
                    else if (Strings.getTimes(name, '/') == 1 && Strings.endsWith(name, "/"))
                        fileNameList.add(name);
                    break;
                }
                case 1:
                {//文件
                    if (name.indexOf("/") == -1)
                        fileNameList.add(name);
                    break;
                }
                case 2:
                {//目录，要求/只有一个且在最后
                    if (Strings.getTimes(name, '/') == 1 && Strings.endsWith(name, "/"))
                        fileNameList.add(name);
                    break;
                }
                }
            }
        }
        return fileNameList;
    }
    
    /**
     * 生成静态属性，要求静态属性名和配置的properties名称相同
     * 
     * @param cls             类结构
     */
    public static void buildStaticProperties(Class<?> cls)
    {
        buildStaticProperties(cls, cls.getName());
    }
    
    /**
     * 生成静态属性，要求静态属性名和配置的properties名称相同
     * 
     * @param cls               类结构
     * @param propertiesPath    属性文件类路径，格式(org.zhiqim.example.Clazz)不含后缀.properties
     */
    public static void buildStaticProperties(Class<?> cls, String propertiesPath)
    {
        ResourceBundle bundle = ResourceBundle.getBundle(propertiesPath, Locale.getDefault(), cls.getClassLoader());
        
        for (String name : bundle.keySet())
        {
            Field field = Classes.getField(cls, name);
            if (field == null)
                continue;
            
            Classes.setFieldValue(null, field, bundle.getString(name));
        }
    }
    
    /**
     * 新建对象，生成属性，要求属性名和配置的properties名称相同
     * 
     * @param cls               类结构
     * @param propertiesPath    属性文件类路径，格式(org.zhiqim.example.Clazz)不含后缀.properties
     */
    public static <T> T buildProperties(Class<T> cls)
    {
        return buildProperties(cls, cls.getName());
    }
    
    /**
     * 新建对象，生成属性，要求属性名和配置的properties名称相同
     * 
     * @param cls               类结构
     * @param propertiesPath    属性文件类路径，格式(org.zhiqim.example.Clazz)不含后缀.properties
     */
    public static <T> T buildProperties(Class<T> cls, String propertiesPath)
    {
        ResourceBundle bundle = ResourceBundle.getBundle(propertiesPath, Locale.getDefault(), cls.getClassLoader());
        
        T obj = Classes.newInstance(cls);
        for (String name : bundle.keySet())
        {
            Field field = Classes.getField(cls, name);
            if (field == null)
                continue;
            
            Classes.setFieldValue(obj, field, bundle.getString(name));
        }
        
        return obj;
    }
}
